package base_app

import (
	"context"
	"errors"
	"fmt"
	baseConfig "gitee.com/zaiqiang231/go-base-app/base_app/config"
	baseLog "gitee.com/zaiqiang231/go-base-app/base_app/log"
	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
	"net/http"
	"strings"
	"time"
)

type ApiServer struct {
	basename string
	gs       *GracefulShutdown
	*gin.Engine
	serverConfig *baseConfig.ServerConfig
	httpServer   *http.Server
	httpsServer  *http.Server
}

type ServerOption func(*ApiServer)

type RouterFunc func(g *gin.Engine)

type StoreFunc func()

func WithRouter(router RouterFunc) ServerOption {
	return func(server *ApiServer) {
		router(server.Engine)
	}
}

func WithGracefulShutdown(gs *GracefulShutdown) ServerOption {
	return func(server *ApiServer) {
		server.gs = gs
	}
}

func WithServerConfig(serverConfig *baseConfig.ServerConfig) ServerOption {
	return func(server *ApiServer) {
		server.serverConfig = serverConfig
	}
}

func CreateAPIServer(basename string, opts ...ServerOption) *ApiServer {
	server := &ApiServer{
		basename: basename,
		Engine:   gin.New(),
	}

	for _, opt := range opts {
		opt(server)
	}

	server.GET("/ping", func(c *gin.Context) {
		c.String(200, server.basename+" is started")
	})

	return server
}

func (s *ApiServer) PrepareRun() *ApiServer {

	if s.serverConfig == nil {
		baseLog.Errorf("ApiServer PrepareRun check serverConfig err: ", s.serverConfig)
	}

	s.gs.AddShutdownCallback(ShutdownFunc(func() error {
		baseLog.Debugf("%v api server start shutdown", s.basename)
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		if s.httpServer != nil {
			if err := s.httpServer.Shutdown(ctx); err != nil {
				baseLog.Fatalf("%v http Server.Shutdown err: %v", s.basename, err)
				return err
			}
		}
		if s.httpsServer != nil {
			if err := s.httpsServer.Shutdown(ctx); err != nil {
				baseLog.Fatalf("%v https Server.Shutdown err: %v", s.basename, err)
				return err
			}
		}

		return nil
	}))
	return s
}

func (s *ApiServer) Run(eg *errgroup.Group) {

	if s.serverConfig.HttpConfig != nil {
		s.httpServer = &http.Server{
			Addr:    s.serverConfig.HttpConfig.Addr,
			Handler: s,
		}
	}

	if s.serverConfig.HttpsConfig != nil {
		s.httpsServer = &http.Server{
			Addr:    s.serverConfig.HttpsConfig.Addr,
			Handler: s,
		}
	}

	eg.Go(func() error {
		if s.httpServer != nil {
			if err := s.httpServer.ListenAndServe(); err != nil {
				if !errors.Is(err, http.ErrServerClosed) {
					baseLog.Fatalf("%v http Server.ListenAndServe failed: %s", s.basename, err.Error())
				}
			}
		}
		return nil
	})

	eg.Go(func() error {
		if s.httpsServer != nil {
			if err := s.httpsServer.ListenAndServeTLS(s.serverConfig.HttpsConfig.TlsConfig.CertFile,
				s.serverConfig.HttpsConfig.TlsConfig.PrivateKeyFile); err != nil {
				if !errors.Is(err, http.ErrServerClosed) {
					baseLog.Fatalf("%v https Server.ListenAndServe failed: %s", s.basename, err.Error())
				}
			}
		}
		return nil
	})

	eg.Go(func() error {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if testHealth {
			if err := s.ping(ctx); err != nil {
				baseLog.Fatalf("testHealth err: ", err.Error())
			}
		}
		return nil
	})

}

func (s *ApiServer) ping(ctx context.Context) error {
	addr := s.serverConfig.HttpConfig.Addr
	url := fmt.Sprintf("http://%s/ping", addr)
	if strings.Contains(addr, "0.0.0.0") {
		url = fmt.Sprintf("http://127.0.0.1:%s/ping", strings.Split(addr, ":")[1])
	}

	for {
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			return err
		}
		resp, err := http.DefaultClient.Do(req)
		if err == nil && resp.StatusCode == http.StatusOK {
			baseLog.Debugf("The router has been deployed successfully. ok")
			resp.Body.Close()
			return nil
		}
		baseLog.Debugf("Waiting for the router, retry in 1 second.")
		time.Sleep(1 * time.Second)

		select {
		case <-ctx.Done():
			baseLog.Fatalf("can not ping http server within the specified time interval.")
		default:
		}
	}

	return nil
}
